﻿using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;

using System.Threading;

namespace Gadgeteer.Modules.GHIElectronics
{
    /// <summary>
    /// A simple character display module for Microsoft .NET Gadgeteer
    /// </summary>
    /// <example>
    /// <para>The following example uses a <see cref="Display_HD44780"/> object to display "Hello World" on the character display. 
    /// First, we clear the screen and set the cursor to the top left corner.
    /// Then, we display our desired text to the screen.
    /// </para>
    /// <code>
    /// using System;
    /// using System.Collections;
    /// using System.Threading;
    /// using Microsoft.SPOT;
    /// using Microsoft.SPOT.Presentation;
    /// using Microsoft.SPOT.Presentation.Controls;
    /// using Microsoft.SPOT.Presentation.Media;
    /// using Microsoft.SPOT.Touch;
    ///
    /// using Gadgeteer.Networking;
    /// using GT = Gadgeteer;
    /// using GTM = Gadgeteer.Modules;
    /// using Gadgeteer.Modules.GHIElectronics;
    ///
    /// namespace TestApp
    /// {
    ///     public partial class Program
    ///     {
    ///         void ProgramStarted()
    ///         {
    ///             display_HD44780.Clear();
    ///             display_HD44780.CursorHome();
    ///
    ///             display_HD44780.PrintString("Hello World");
    ///
    ///             Debug.Print("Program Started");
    ///         }
    ///     }
    /// }
    /// </code>
    /// </example>
    public class Display_HD44780 : GTM.Module
    {
        private GT.Interfaces.DigitalOutput LCD_RS;
        private GT.Interfaces.DigitalOutput LCD_E;

        private GT.Interfaces.DigitalOutput LCD_D4;
        private GT.Interfaces.DigitalOutput LCD_D5;
        private GT.Interfaces.DigitalOutput LCD_D6;
        private GT.Interfaces.DigitalOutput LCD_D7;

        private GT.Interfaces.DigitalOutput BackLight;

        const byte DISP_ON = 0xC;    //Turn visible LCD on
        const byte CLR_DISP = 1;      //Clear display
        const byte CUR_HOME = 2;      //Move cursor home and clear screen memory
        const byte SET_CURSOR = 0x80;   //SET_CURSOR + X : Sets cursor position to X

        private void WriteNibble(byte b)
        {
            LCD_D7.Write((b & 0x8) != 0);
            LCD_D6.Write((b & 0x4) != 0);
            LCD_D5.Write((b & 0x2) != 0);
            LCD_D4.Write((b & 0x1) != 0);

            LCD_E.Write(true); LCD_E.Write(false); //Toggle the Enable Pin
            Thread.Sleep(1);
        }

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="socketNumber">The socket that this module is plugged in to.</param>
        public Display_HD44780(int socketNumber)
        {
            // This finds the Socket instance from the user-specified socket number.  
            // This will generate user-friendly error messages if the socket is invalid.
            // If there is more than one socket on this module, then instead of "null" for the last parameter, 
            // put text that identifies the socket to the user (e.g. "S" if there is a socket type S)
            Socket socket = Socket.GetSocket(socketNumber, true, this, null);
            socket.EnsureTypeIsSupported('Y', this);

            LCD_RS = new GT.Interfaces.DigitalOutput(socket, GT.Socket.Pin.Four, false, null);
            LCD_E = new GT.Interfaces.DigitalOutput(socket, GT.Socket.Pin.Three, false, null);
            LCD_D4 = new GT.Interfaces.DigitalOutput(socket, GT.Socket.Pin.Five, false, null);
            LCD_D5 = new GT.Interfaces.DigitalOutput(socket, GT.Socket.Pin.Seven, false, null);
            LCD_D6 = new GT.Interfaces.DigitalOutput(socket, GT.Socket.Pin.Nine, false, null);
            LCD_D7 = new GT.Interfaces.DigitalOutput(socket, GT.Socket.Pin.Six, false, null);

            BackLight = new GT.Interfaces.DigitalOutput(socket, GT.Socket.Pin.Eight, true, null);

            Initialize();
        }

        /// <summary>
        /// This function will enable the display, clean it, and enter 4-bit mode.
        /// </summary>
        private void Initialize()
        {
            SendCmd(0x33);
            SendCmd(0x32);

            SendCmd(DISP_ON);
            SendCmd(CLR_DISP);

            Thread.Sleep(3);
        }

        ///<summary>
        /// Sends an LCD command.
        /// </summary>
        private void SendCmd(byte c)
        {
            LCD_RS.Write(false); //set LCD to data mode

            WriteNibble((byte)(c >> 4));

            WriteNibble(c);

            Thread.Sleep(2);

            LCD_RS.Write(true); //set LCD to data mode  
        }

        /// <summary>
        /// Prints the passed in string to the screen at the current cursor position. Note: This function will move the cursor position after.
        /// </summary>
        /// <param name="str">The string to print.</param>
        public void PrintString(string str)
        {
            for (int i = 0; i < str.Length; i++)
                Putc((byte)str[i]);
        }

        /// <summary>
        /// Sends an ASCII character to the LCD at the current cursor position. Note: This function will move the cursor position after.
        /// </summary>
        /// <param name="c">The character to display.</param>
        public void Putc(byte c)
        {
            WriteNibble((byte)(c >> 4));

            WriteNibble(c);
        }

        /// <summary>
        /// Clears the screen.
        /// </summary>
        public void Clear()
        {
            SendCmd(CLR_DISP);
            Thread.Sleep(2);
        }

        /// <summary>
        /// Places the cursor at the top left of the screen.
        /// </summary>
        public void CursorHome()
        {
            SendCmd(CUR_HOME);
            Thread.Sleep(2);
        }

        /// <summary>
        /// Set the cursor to the passed in position.
        /// </summary>
        /// <param name="row">Row of the desired cursor position.</param>
        /// <param name="col">Column of the desired cursor position.</param>
        public virtual void SetCursor(byte row, byte col)
        {
            byte[] row_offsets = new byte[4] { 0x00, 0x40, 0x14, 0x54 };
            SendCmd((byte)(SET_CURSOR | row_offsets[row] | col));
        }

        /// <summary>
        /// Sets the module's backlight to the passed in value.
        /// </summary>
        /// <param name="bOn">True for backlight enabled, false for disabled.</param>
        public void SetBacklight(bool bOn)
        {
            BackLight.Write(bOn);
        }
    }
}
